Chapter 17 EXCEPTIONS EXCEPTIONS ARE NOT NEW TO YOU _________________________________________________________________ Assuming you have completed part 1 of this tutorial, you have seen many references to exceptions, but we have said little about how you can use them. The purpose of this chapter is to instruct you on the use of exceptions, and by the time you complete it, you will have the ability to use exceptions to develop a program with its own error handling ability. WHY DO WE NEED EXCEPTIONS? _________________________________________________________________ The original charter for the development of Ada included the ability to operate in a real-time environment. You already understand, if you have much programming experience, that if it is possible for an error to surface, it will eventually surface. Many programming languages simply terminate operation if a "fatal error" is detected, but this could be a disaster if the program was controlling a real-time system upon which human lives or safety depended. A 747 in final approach to the airport, or a system used in a hospital operating room would be two such examples of systems that simply could not be terminated abruptly because a bad data point was somehow accumulated. The careful application of Ada exceptions will allow the software to gracefully recover from such a situation rather than aborting operation completely. OUR FIRST EXCEPTION _________________________________________________________________ Examine the program named EXCEPT1.ADA for our =============== first example program with an exception handler. EXCEPT1.ADA Ignore lines 21 and 22 for the moment and you =============== will have a program that is not at all unusual, and should pose no problem for you to understand. The program does have a carefully introduced error however, because when we reach a value of 4 for Index in line 17, we will be attempting to divide by zero. Dividing by zero is not allowed in any programming language, because the answer is infinite and therefore undefined. The Ada runtime system will, by definition, cause the exception named Numeric_Error to be raised, which is the Ada way of saying that a divide by zero was attempted. This signals the system to do something about it. (Actually, there are many other ways to get the Numeric_Error exception raised but we will worry about them later.) Page 17-1 Chapter 17 - Exceptions The Ada system will search, in a very definite way, for any instructions we have given about this error, and if it finds none, will terminate operation of the program after issuing a message concerning the error. If we have given instructions about what to do with the error, it will execute the instructions and continue operation as we direct it to do. The method of giving the system these instructions is illustrated in lines 21 and 22. HOW ARE EXCEPTIONS HANDLED? _________________________________________________________________ When any exception is raised, the system immediately looks at the end of the current block or subprogram for the reserved word exception. If it is found, and if the specific exception that was raised is defined there, the instructions associated with that exception are executed, and the subprogram or block is exited. To define a handler for a specific exception, the reserved word when is used, followed by the name of the exception, and finally the sequence of statements to be executed following the => operator. The sequence of statements can be of arbitrary complexity, but should be kept simple due to the nature of exception handling. In this case, we output a message to the monitor and do nothing else. As many different exceptions as desired can be handled at the end of any block or subprogram by adding additional constructs of the form, when => instructions; following the single instance of the reserved word exception. We will study examples of multiple exception handlers later in this chapter. WHAT HAPPENS FOLLOWING EXCEPTION HANDLING? _________________________________________________________________ Following handling of the exception, the program executes an otherwise normal return to the calling program, and the normal sequence of instructions in the calling program is executed. Note that it is impossible to jump back into the subprogram or block in which the exception was raised from the exception handling routine at the end of that block. In this case, because of the logic used, the loop defined in line 13 is terminated early because we essentially jumped out of the loop and the program is ended. If an exception handler or a group of exception handlers is included, it must be the last thing in the block. If normal execution of the block reaches the end of the executable statements by coming to the reserved word exception, the block is terminated normally. You cannot drop into the exception handler at the end of a block. The only way to get to the exception handling code is through raising an exception. Page 17-2 Chapter 17 - Exceptions In spite of the additional questions you have at this point, compile and execute this program. Observe the results, and if you do not understand the output, reread the above text until you do, because these fundamental points are essential to understanding the entire topic of exceptions. LET'S USE SEVERAL EXCEPTIONS _________________________________________________________________ Examine the program named EXCEPT2.ADA for =============== additional examples of exceptions. This program EXCEPT2.ADA will answer many of your questions about =============== exceptions. The portion of the program defined in lines 46 through 50, along with its associated procedure is logically identical to the last example program except for the loop being moved to the calling program. When the divide by zero is detected by the system, which raises the Numeric_Error exception, the exception is handled by the exception handler defined in lines 20 and 21, and the return to the calling program is effected. In this case however, when control returns to the calling program, we are still inside of the loop, and the loop completes normally. This should indicate to you that by careful selection of where you handle exceptions, you can control the overall result. We will see more about this as we continue our study of exceptions. The logic of the second group of instructions, found in lines 52 through 67, is identical to the logic of the first group as studied in the last paragraph. The only difference is that the procedure has been changed into a block and inserted into the code in an inline fashion. This has been done to illustrate the use of an exception in a block of code, and to illustrate that the exception handler for the block of code is put at the end of that block. After the exception is raised and handled, execution begins at the first statement following the block. Because the block is contained within the loop, the exception is handled within the loop and the loop runs to completion. MULTIPLE EXCEPTION HANDLERS _________________________________________________________________ Finally, we come to the section of code in lines 69 through 72, consisting of a simple loop calling the procedure New_Divide_Loop. The procedure itself, defined in lines 24 through 43, contains an example of a new operation, the ability to make up our own exception, raise it ourself, and handle it with our own exception handler. Page 17-3 Chapter 17 - Exceptions Line 25 declares the identifier My_Own_Exception as being the name of an exception, and is defined in much the same way that we would declare a variable. We cannot assign a value to it, but we can raise it anywhere within its defined scope which is the same as the scope of a variable declared at the same place. The exception is automatically initialized to the "not raised" condition by the system. Beginning in line 37, we define three different exception handlers, which will cover any exceptions raised anywhere within this procedure. The first two are named exception handlers but the third handler uses the reserved word others to indicate that it will be used for any exceptions that are not handled by the two named exception handlers. The others clause is optional, but if it is included, it must be last. RAISING AN EXCEPTION _________________________________________________________________ If we reach line 31 with a value of 4, which we eventually will because of the logic of the calling program, we will detect the divide by zero that would be attempted upon reaching line 34. Instead of letting the system generate the exception named Numeric_Error, we generate our own exception named My_Own_Exception, using the reserved word raise followed by the name of the exception. As soon as we raise this exception, the system jumps to the end of the block, looks for the reserved word exception, which it finds, then looks for the exception handler with the name that was raised. Upon finding it, the statements are executed, resulting in a message being output to the display, and a return is effected to the calling program. In this case, the system will not raise the exception Numeric_Error, because we are detecting the error before it actually happens. You could raise it yourself by inserting the statement "raise Numeric_Error;" somewhere in this procedure, possibly when the value of Index is equal to 3. It would be a good exercise for you to insert that in the code to see that you can raise one of the system exceptions as well as your own. Be sure to compile and execute this program to verify proper operation according to this description and to ascertain your understanding of the same. Note that if an exception occurs, formal parameters of mode out or in out are not updated since a normal return is not accomplished. Partial results will therefore not be returned to the calling program. This is not illustrated here, but is left for the student to investigate if desired. Page 17-4 Chapter 17 - Exceptions WHAT ARE THE PREDEFINED EXCEPTIONS? _________________________________________________________________ There are five predefined exceptions which can be raised by the system to indicate a very specific problem. A brief definition follows; Constraint_Error - This will occur if something goes out of its assigned range. Numeric_Error - This will occur if something goes wrong with arithmetic such as the attempt to divide by zero illustrated in this chapter. Program_Error - This will occur if we attempt to violate an Ada control structure such as dropping through the bottom of a function without a return. Storage_Error - This will occur if we run out of storage space through either recursive calls or storage allocation calls. Tasking_Error - This will occur when attempting to use some form of tasking in violation of the rules. All five of these exceptions are defined in detail in section 11.1 of the LRM. WHAT ABOUT AN UNHANDLED EXCEPTION? _________________________________________________________________ Examination of the program named EXCEPT3.ADA =============== will reveal what happens if an exception is EXCEPT3.ADA raised that is not handled by the program. In =============== a word, the program will be terminated, but we need to understand how termination occurs so we can intelligently prevent it. There is a loop in the main program which calls two procedures successively, Divide_By_Zero and Raise_An_Error. The first procedure is identical to that in the first two example programs and the only exception raised is Numeric_Error, which is handled properly. The second procedure has its own exception defined, named My_Own_Error which it raises and handles itself in the manner defined previously in this chapter. It also has a divide by zero problem in line 29 that will raise the exception Numeric_Error when Count is equal to 6. Of course, the logic is defined to make this happen and illustrate the error. Page 17-5 Chapter 17 - Exceptions PROPAGATION OF EXCEPTIONS _________________________________________________________________ When the exception Numeric_Error is raised at line 29, the system searches for the reserved word exception which it finds in line 34 at the end of the procedure. It then searches for a sequence of statements for Numeric_Error which it does not find. Since an exception handler is not found within the procedure, the exception is propagated to the calling program in such a way that the exception appears to have been raised by the calling statement. In this case it will appear to the logic as if the exception Numeric_Error was raised by the statement in line 42. Once again, the exception rules are applied, and the system searches for an exception block at the end of the block or subprogram, in this case being the main program. Finding the reserved word exception in line 46, the system looks for the desired exception handler, which it finds and executes, then drops out of the bottom of the main program which initiates a return to the operating system. If there were no handler for the exception, the exception would be propagated to the operating system, and it would issue some sort of nasty message about an unhandled exception leading to program termination. It should be somewhat obvious to you that if you added another level of subprogram nesting, you could report the error yourself, and possibly recover operation of the program. It is all a matter of program definition. CAN YOU EXECUTE AN EXCEPTION WITHOUT RAISING IT? _________________________________________________________________ As mentioned before, the section of code at the end of the program, following the reserved word exception, is never executed without raising an exception. It can never be executed by dropping into it. Be sure to compile and execute this program to observe the operation of the exceptions. EXCEPTIONS CAN OCCUR DURING DECLARATIONS _________________________________________________________________ Examine the program named EXCEPT4.ADA for an =============== example of an exception that occurs during the EXCEPT4.ADA declaration part of the program. =============== When a procedure is called, its declarations are elaborated prior to the logic being executed, as we have stated before. If one of the declarations cannot be properly elaborated, then an error occurs and an exception is raised. Examining the procedure Try_It will reveal an error in lines 8 through 10, where Page 17-6 Chapter 17 - Exceptions the variable Funny is declared to be of type LIMIT_RANGE with limits of 14 through 23, then it is initialized to the value 8. Since this is out of the allowed range, the exception Constraint_Error will be raised. The executable part of the procedure is not yet ready for use, so the exception handler defined within it cannot be used, and the exception will be propagated to the calling program where it will be handled just as if it occurred in the calling statement, which is line 22 in this case. The exception will therefore be handled by lines 24 through 26 of the procedure Try_To_Fix_It. Be sure to compile and run this program, then study the results. ADDITIONAL PREDEFINED EXCEPTIONS _________________________________________________________________ You will find that there are actually additional exceptions predefined by your compiler, but these are all defined in additional packages supplied with your compiler. Packages such as Text_IO, Sequential_IO, or Calendar (to be discussed later with tasking), have some number of exceptions defined as a part of their interfaces, but there are only five exceptions predefined as a part of Ada. These were listed and discussed earlier. A FEW MORE TOPICS CONCERNING EXCEPTIONS _________________________________________________________________ The example program named EXCEPT5.ADA =============== illustrates a few additional topics about EXCEPT5.ADA exceptions and illustrates how they are used in =============== a package. This is a very strange program with lots of exception handling examples for your study. You will be left on your own to study the overall operation of this program, but the unique exception handling techniques will be pointed out to you. The package body contains a section of initialization code in lines 37 through 48 which is composed of nothing but a null statement and several exception handlers. These are only used during initialization of the package since they are not within the executable portion of either of the subprograms. You will notice that the exception named Funny_Add_Error is declared in the package specification so it is visible in the exception handler in line 44, but the exception named Funny_Subtract_Error is not visible there because it is declared within the function. We will see soon however, that even this exception can be propagated to the main program. When the program is executing, a call to the function Subtract_One raises the exception Funny_Subtract_Error which is handled by the exception handler at the end of the function in line 32. A message is displayed and the same exception is raised by the isolated raise statement in line 34. This statement simply raises the exception Page 17-7 Chapter 17 - Exceptions that caused the jump to the exception handler in the first place. The isolated raise statement can only be used within an exception handler. The exception is passed on to the calling program. Because the exception named Funny_Subtract_Error is not visible to the main program, it cannot handle it by name but even this exception can be handled by an others clause as is done in line 66. After printing a message, the same exception is once again raised in line 68 where it is passed on to the operating system. You will see when you execute this program that the exception is known by name to the operating system. It will give you a nasty message about an unhandled exception and terminate operation. THE others CLAUSE IN AN EXCEPTION HANDLER _________________________________________________________________ If the others clause is used, it must be the last exception handler and it cannot be combined with any other exceptions such as illustrated in line 62. As in other Ada constructs, two or more exceptions can be "or"ed and use the same exception handler. Note line 57 where an exception is renamed to reduce the length of its name. Any exception can be renamed in a similar fashion. Be sure to compile and execute this program and spend the time necessary to understand the exception propagation illustrated here. PROGRAMMING EXERCISES _________________________________________________________________ 1. Change line 34 of EXCEPT2.ADA to cause a divide by zero when Index is equal to 2 and see that both exceptions will be handled correctly. 2. Also in EXCEPT2.ADA, declare a new exception in line 25, and raise it in line 32 to see how the others clause handles it. Page 17-8